using System;
using System.Collections.Generic;
using UnityEngine;

namespace MeshCombine
{
	public static class MeshCombineUtil
	{
		// 合并分割模式
		public enum SplitMode
		{
			None,
			ByShader,
			ByMaterial
		}
		
		private delegate T RendererPropertySelector<T>(Renderer renderer);

		public delegate void UnwrapMethod(Mesh mesh, float hardAngle, float margin);

		public delegate void ProcessStepCompleted(int stepsDone, int overallSteps, float progress, bool combining);

		public static UnwrapMethod Unwrapper;

		public static float Threshold = 0.0001f;

		public static float DirectionThreshold = 0.001f;

		private static int overallSteps;

		private static int finishedSteps;

		public static event ProcessStepCompleted OnProcessStepCompleted;
		public static void TranformToSpace(Transform parent, Mesh mesh)
		{
			Matrix4x4 worldToLocalMatrix = parent.worldToLocalMatrix;
			Vector3[] vertices = mesh.vertices;
			Vector3[] normals = mesh.normals;
			Vector4[] tangents = mesh.tangents;
			for (int i = 0; i < vertices.Length; i++)
			{
				Vector3 val = vertices[i];
				Vector3 val2 = normals[i];
				Vector4 val3 = tangents[i];
				float w = val3.w;
				vertices[i] = parent.InverseTransformPoint(val);
				ref Vector3 reference = ref normals[i];
				Vector3 val4 = parent.InverseTransformDirection(val2);
				reference = val4.normalized;
				val4 = parent.InverseTransformDirection(new Vector3(val3.x, val3.y, val3.z));
				val3 = val4.normalized;
				val3.w = w;
				tangents[i] = val3;
			}
			mesh.vertices=vertices;
			mesh.normals=normals;
			mesh.tangents=tangents;
			mesh.RecalculateBounds();
		}

		/// <summary>
		/// 合并列表内所有网格，产生并返回新对象包括网格和材质
		/// </summary>
		public static GameObject CombineInstatiate(IList<GameObject> gameObjects, Transform parent, SplitMode splitMode = SplitMode.None,   bool recalculateNormals = false)
		{
			GameObject gameObject = null;
			if (splitMode == SplitMode.None)
			{
				InitializeProgressReport(gameObjects.Count);
				gameObject = CombineInstatiate(gameObjects, parent, recalculateNormals);
			}
			else if (splitMode == SplitMode.ByShader || splitMode == SplitMode.ByMaterial)
			{
				string name = "Combined";
				int siblingIndex = 0;
				gameObject = new GameObject();
				switch (splitMode)
				{
					case SplitMode.ByShader:
						{
							Dictionary<Shader, IList<GameObject>> objectsByShader = GetObjectsByShader(gameObjects);
							InitializeProgressReport(objectsByShader);
							foreach (Shader key in objectsByShader.Keys)
							{
								IList<GameObject> gameObjects2 = objectsByShader[key];
								GameObject val2 = CombineInstatiate(gameObjects2, parent, recalculateNormals);
								val2.name = key.ToString();
								val2.transform.SetParent(gameObject.transform);
							}
							break;
						}
					case SplitMode.ByMaterial:
						{
							Dictionary<Material, IList<GameObject>> objectsByMaterial = GetObjectsByMaterial(gameObjects);
							InitializeProgressReport(objectsByMaterial);
							foreach (Material key2 in objectsByMaterial.Keys)
							{
								IList<GameObject> gameObjects2 = objectsByMaterial[key2];
								GameObject val2 = CombineInstatiate(gameObjects2, parent, recalculateNormals);
								val2.name = key2.ToString();
								val2.transform.SetParent(gameObject.transform);
							}
							break;
						}
				}

				gameObject.name = name;
				gameObject.transform.SetSiblingIndex(siblingIndex);
			}
			ForceProcessCompleted();
			return gameObject;
		}

		public static GameObject CombineInstantiate(IList<GameObject> gameObjects, Transform parent, bool recalculateNormals)
		{
			return CombineInstatiate(gameObjects, parent, recalculateNormals);
		}

		public static GameObject CombineInstatiate(IList<GameObject> gameObjects, Transform parent,bool recalculateNormals)
		{
			string text = "Combined";
			int siblingIndex = 0;
			Transform parent2 = null;
			GameObject go = new GameObject();

//			CombineShadow combineShadow = go.AddComponent<CombineShadow>();
			MeshFilter filter = go.AddComponent<MeshFilter>();
			MeshRenderer meshRenderer = go.AddComponent<MeshRenderer>();
			CombinedMeshData combinedMeshData = Combine(gameObjects, recalculateNormals);
			filter.mesh = combinedMeshData.CombinedMesh;
			meshRenderer.materials = combinedMeshData.Materials;
//			combineShadow.Originals = new List<GameObject>(gameObjects).ToArray();
			if (parent != null)
			{
				text = text + " [" + parent.name + "]";
				parent2 = parent.transform.parent;
				siblingIndex = parent.GetSiblingIndex();
				parent.gameObject.SetActive(false);
//				combineShadow.Parent = parent.gameObject;
			}
			else
			{
//				SetActiveAll(gameObjects, false);
			}

			go.name = text;
			go.transform.parent = parent2;
			go.transform.SetSiblingIndex(siblingIndex);

			return go;
		}

		public static CombinedMeshData Combine(IList<GameObject> gameObjects, bool recalculateNormals)
		{
			CombinedMeshData combinedMeshData = new CombinedMeshData();
			int count;
			Dictionary<Material, List<MeshTransform>> materialMeshes = GetMaterialMeshes(gameObjects, out count);
			Mesh mesh = new Mesh();
			mesh.name = "Combined Mesh";
			Dictionary<Material, MeshTransform> dictionary = CollapseMaterials(materialMeshes);
			List<CombineInstance> list = new List<CombineInstance>();
			Material[] array = new List<Material>(dictionary.Keys).ToArray();
            foreach (Material key in array)
			{
				MeshTransform meshTransform = dictionary[key];
				CombineInstance item = default;
				item.mesh = meshTransform.Mesh;
				item.transform = meshTransform.LocalToWorld;
				list.Add(item);
			}
			CallProcessStepCompleted(true);
			mesh.CombineMeshes(list.ToArray(), false, true);
			if (recalculateNormals)
			{
				mesh.RecalculateNormals();
			}
			
			mesh.RecalculateBounds();
			combinedMeshData.CombinedMesh = mesh;
			combinedMeshData.Materials = array;
			return combinedMeshData;
		}


		private static void Weld(Mesh mesh, float threshold, float thresholdDirection, float bucketStep)
		{

			Vector3[] vertices = mesh.vertices;
			Vector3[] normals = mesh.normals;
			Vector3[] array = (Vector3[])(object)new Vector3[vertices.Length];
			Vector3[] array2 = (Vector3[])(object)new Vector3[vertices.Length];
			int[] array3 = new int[vertices.Length];
			int num = 0;
			Vector3 val = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
			Vector3 val2 = new Vector3(float.MinValue, float.MinValue, float.MinValue);
			for (int i = 0; i < vertices.Length; i++)
			{
				if (vertices[i].x < val.x)
				{
					val.x = vertices[i].x;
				}
				if (vertices[i].y < val.y)
				{
					val.y = vertices[i].y;
				}
				if (vertices[i].z < val.z)
				{
					val.z = vertices[i].z;
				}
				if (vertices[i].x > val2.x)
				{
					val2.x = vertices[i].x;
				}
				if (vertices[i].y > val2.y)
				{
					val2.y = vertices[i].y;
				}
				if (vertices[i].z > val2.z)
				{
					val2.z = vertices[i].z;
				}
			}
			int num2 = Mathf.FloorToInt((val2.x - val.x) / bucketStep) + 1;
			int num3 = Mathf.FloorToInt((val2.y - val.y) / bucketStep) + 1;
			int num4 = Mathf.FloorToInt((val2.z - val.z) / bucketStep) + 1;
			List<int>[,,] array4 = new List<int>[num2, num3, num4];
			for (int i = 0; i < vertices.Length; i++)
			{
				int num5 = Mathf.FloorToInt((vertices[i].x - val.x) / bucketStep);
				int num6 = Mathf.FloorToInt((vertices[i].y - val.y) / bucketStep);
				int num7 = Mathf.FloorToInt((vertices[i].z - val.z) / bucketStep);
				if (array4[num5, num6, num7] == null)
				{
					array4[num5, num6, num7] = new List<int>();
				}
				int num8 = 0;
				while (true)
				{
					if (num8 < array4[num5, num6, num7].Count)
					{
						int num9 = array4[num5, num6, num7][num8];
						Vector3 val3 = array[num9] - vertices[i];
						float num10 = Vector3.Dot(array2[num9], normals[i]);
						if (Vector3.SqrMagnitude(val3) < threshold)
						{
							array3[i] = array4[num5, num6, num7][num8];
							break;
						}
						num8++;
						continue;
					}
					array[num] = vertices[i];
					array2[num] = normals[i];
					array4[num5, num6, num7].Add(num);
					array3[i] = num;
					num++;
					break;
				}
			}
			int[] triangles = mesh.triangles;
			int[] array5 = new int[triangles.Length];
			for (int i = 0; i < triangles.Length; i++)
			{
				array5[i] = array3[triangles[i]];
			}
			Vector3[] array6 = (Vector3[])(object)new Vector3[num];
			Vector3[] array7 = (Vector3[])(object)new Vector3[num];
			for (int i = 0; i < num; i++)
			{
				array6[i] = array[i];
				array7[i] = array2[i];
			}
			mesh.Clear();
			mesh.vertices = array6;
			mesh.normals = array7;
			mesh.triangles = array5;
			mesh.RecalculateNormals();
		}

		public static void SetActiveAll(IList<GameObject> objects, bool active)
		{
			foreach (GameObject obj in objects)
			{
				if (obj != null)
				{
					if (!obj.Equals(null))
					{
						obj.SetActive(active);
					}
					else
					{
						Debug.LogError("Combine: " + obj.name + " was destroyed.");
					}
				}
			}
		}

		private static Dictionary<Material, IList<GameObject>> GetObjectsByMaterial(IList<GameObject> gameObjects)
		{
			RendererPropertySelector<Material> selector = (Renderer renderer) => renderer.sharedMaterial;
			return GetObjectsByProperty(gameObjects, selector);
		}

		private static Dictionary<Shader, IList<GameObject>> GetObjectsByShader(IList<GameObject> gameObjects)
		{
			RendererPropertySelector<Shader> selector = (Renderer renderer) => renderer.sharedMaterial.shader;
			return GetObjectsByProperty(gameObjects, selector);
		}

		private static Dictionary<T, IList<GameObject>> GetObjectsByProperty<T>(IList<GameObject> gameObjects, RendererPropertySelector<T> selector)
		{
			Dictionary<T, IList<GameObject>> dictionary = new Dictionary<T, IList<GameObject>>();
			IList<T> properties = GetProperties(gameObjects, selector);
			foreach (T item in properties)
			{
				if (item != null)
				{
					dictionary.Add(item, new List<GameObject>());
				}
			}
			foreach (GameObject gameObject in gameObjects)
			{
				Renderer component = gameObject.GetComponent<Renderer>();
				if (component != null)
				{
					T val = selector(component);
					if (val != null)
					{
						dictionary[val].Add(gameObject);
					}
				}
			}
			return dictionary;
		}

		private static Dictionary<Material, MeshTransform> CollapseMaterials(Dictionary<Material, List<MeshTransform>> mappings)
		{
			Dictionary<Material, MeshTransform> dictionary = new Dictionary<Material, MeshTransform>();
			foreach (Material key in mappings.Keys)
			{
				if (!dictionary.ContainsKey(key))
				{
					dictionary.Add(key, new MeshTransform());
				}
				MeshTransform meshTransform = dictionary[key];
				meshTransform.LocalToWorld = Matrix4x4.identity;
				meshTransform.Mesh = (Mesh)(object)new Mesh();
				List<CombineInstance> list = new List<CombineInstance>();
				foreach (MeshTransform item2 in mappings[key])
				{
					CombineInstance item = default(CombineInstance);
					item.mesh = item2.Mesh;
					item.transform = item2.LocalToWorld;
					list.Add(item);
					CallProcessStepCompleted(combining: false);
				}
				CallProcessStepCompleted(combining: true);
				meshTransform.Mesh.CombineMeshes(list.ToArray(), true, true);
				
			}
			return dictionary;
		}

		/// <summary>
		/// 返回所有对象中不同的材质列表
		/// </summary>
		/// <returns></returns>
		private static Dictionary<Material, List<MeshTransform>> GetMaterialMeshes(IList<GameObject> gameObjects, out int count)
		{
			count = 0;
			Dictionary<Material, List<MeshTransform>> dictionary = new Dictionary<Material, List<MeshTransform>>();
			foreach (GameObject gameObject in gameObjects)
			{
				Renderer component = gameObject.GetComponent<Renderer>();
				Mesh mesh = gameObject.GetMesh();
				if (component != null && mesh != null)
				{
					for (int i = 0; i < mesh.subMeshCount; i++)
					{
						if (i >= component.sharedMaterials.Length)
						{
							continue;
						}
						Material val = component.sharedMaterials[i];
						if (val != null)
						{
							if (!dictionary.ContainsKey(val))
							{
								dictionary.Add(val, new List<MeshTransform>());
							}
							MeshTransform meshTransform = new MeshTransform();
							meshTransform.LocalToWorld = gameObject.transform.localToWorldMatrix;
							meshTransform.WorldToLocal = gameObject.transform.worldToLocalMatrix;
							meshTransform.Mesh = MeshFromSubMesh(mesh, i);
							dictionary[val].Add(meshTransform);
							count++;
						}
					}
				}
			}
			return dictionary;
		}

		private static Mesh MeshFromSubMesh(Mesh source, int subMesh)
		{
			Mesh mesh = new Mesh();
			int[] triangles = source.GetTriangles(subMesh);
			int num = 0;
			Dictionary<int, int> dictionary = new Dictionary<int, int>();
			foreach (int key in triangles)
			{
				if (!dictionary.ContainsKey(key))
				{
					dictionary.Add(key, num);
					num++;
				}
			}
			int[] array = new int[triangles.Length];
			for (int j = 0; j < triangles.Length; j++)
			{
				array[j] = dictionary[triangles[j]];
			}
			mesh.vertices = GetVertextData(source.vertices, dictionary);
			mesh.normals = GetVertextData(source.normals, dictionary);
			mesh.tangents = GetVertextData(source.tangents, dictionary);
			mesh.uv = GetVertextData(source.uv, dictionary);
			mesh.uv2 = GetVertextData(source.uv2, dictionary);
			mesh.uv3 = GetVertextData(source.uv3, dictionary);
			mesh.uv4 = GetVertextData(source.uv4, dictionary);
			mesh.colors = GetVertextData(source.colors, dictionary);
			mesh.triangles = array;
			return mesh;
		}

		internal static T[] GetVertextData<T>(IList<T> source, Dictionary<int, int> triangles)
		{
			T[] array = null;
			if (source != null && source.Count > 0)
			{
				array = new T[triangles.Count];
				foreach (int key in triangles.Keys)
				{
					int num = triangles[key];
					array[num] = source[key];
				}
			}
			return array;
		}

		internal static T[] GetVertextData<T>(IList<T> source, int[] triangles)
		{
			T[] array = null;
			if (source != null && source.Count > 0)
			{
				array = new T[triangles.Length];
				for (int i = 0; i < triangles.Length; i++)
				{
					int index = triangles[i];
					array[i] = source[index];
				}
			}
			return array;
		}

		internal static int[] GetTriangleData(int[] triangles)
		{
			int[] array = new int[triangles.Length];
			for (int i = 0; i < triangles.Length; i++)
			{
				array[i] = i;
			}
			return array;
		}

		internal static int[] GetUniqueIndices(int[] triangles)
		{
			HashSet<int> hashSet = new HashSet<int>();
			foreach (int item in triangles)
			{
				if (!hashSet.Contains(item))
				{
					hashSet.Add(item);
				}
			}
			return new List<int>(hashSet).ToArray();
		}

		/// <summary>
		/// 返回对象列表中，所有材质的不同属性
		/// </summary>
		/// <returns></returns>
		private static List<T> GetProperties<T>(IList<GameObject> gameObjects, RendererPropertySelector<T> selector)
		{
			HashSet<T> hashSet = new HashSet<T>();
			foreach (GameObject gameObject in gameObjects)
			{
				Renderer component = gameObject.GetComponent<Renderer>();
				if (component != null)
				{
					T val = selector(component);
					if (val != null && !hashSet.Contains(val))
					{
						hashSet.Add(val);
					}
				}
			}
			return new List<T>(hashSet);
		}

		/// <summary>
		/// 返回所有可见对象的子对象
		/// </summary>
		public static IList<GameObject> GetChildObjects(IList<GameObject> gameObjects)
		{
			List<GameObject> list = new List<GameObject>();
			foreach (GameObject gameObject in gameObjects)
			{
				if (gameObject.activeInHierarchy)
				{
					list.Add(gameObject);
					IList<GameObject> list2 = new List<GameObject>();
					foreach (Transform item in gameObject.transform)
					{
						Transform val = item;
						list2.Add(val.gameObject);
					}
					IList<GameObject> childObjects = GetChildObjects(list2);
					list.AddRange(childObjects);
				}
			}
			return list;
		}

		private static void InitializeProgressReport(int overallSteps)
		{
			finishedSteps = 0;
			MeshCombineUtil.overallSteps = overallSteps;
		}

		private static void InitializeProgressReport<T>(Dictionary<T, IList<GameObject>> data)
		{
			finishedSteps = 0;
			overallSteps = 0;
			foreach (KeyValuePair<T, IList<GameObject>> datum in data)
			{
				foreach (GameObject item in datum.Value)
				{
					overallSteps++;
				}
			}
		}

		private static void ForceProcessCompleted()
		{
			if (MeshCombineUtil.OnProcessStepCompleted != null)
			{
				MeshCombineUtil.OnProcessStepCompleted(overallSteps, overallSteps, 1f, combining: false);
			}
		}

		private static void CallProcessStepCompleted(bool combining)
		{
			if (MeshCombineUtil.OnProcessStepCompleted != null)
			{
				finishedSteps = (combining ? finishedSteps : (finishedSteps + 1));
				float progress = (float)finishedSteps / (float)overallSteps;
				MeshCombineUtil.OnProcessStepCompleted(finishedSteps, overallSteps, progress, combining);
			}
		}

        // 获取指定类型的对象数组
        public static IList<GameObject> GetTypesObjects(GameObject go, params Type[] types)
        {
            List<GameObject> objects = new List<GameObject>();
            GameObject[] gos = {go};
            IList<GameObject> allObjects = GetChildObjects(gos);
            if (types.Length > 0)
            {
                foreach (GameObject obj in allObjects)
                {
                    bool check = true;
                    foreach (Type t in types)
                    {
                        Component c = obj.GetComponent(t);
                        check = (check && c != null);
                    }
                    if (check)
                    {
                        objects.Add(obj);
                    }
                }
            }
            else
            {
                objects.AddRange(allObjects);
            }
            return objects;
        }

        public static T AddCollider<T>(IList<GameObject> gameObjects) where T : Collider
        {
            foreach (GameObject item in gameObjects)
            {
	            return AddCollider<T>(item);
            }
            return null;
        }

        public static T AddCollider<T>(Transform transform) where T : Collider
        {
            foreach (Transform item in transform)
            {
                Transform t = item;
                return AddCollider<T>(t.gameObject);
            }
            return null;
        }

        public static T AddCollider<T>(GameObject gameObject) where T : Collider
        {
            if (gameObject.transform.childCount > 0)
            {
	            return AddCollider<T>(gameObject.transform);
            }
            else
            {
                return gameObject.AddComponent<T>();
            }
        }
	}

	[Serializable]
	public class CombinedMeshData
	{
		public Mesh CombinedMesh;

		public Material[] Materials;
	}

	public class MeshTransform
	{
		public Mesh Mesh;

		public Matrix4x4 LocalToWorld;

		public Matrix4x4 WorldToLocal;
	}

	[ExecuteInEditMode]
	public class CombineShadow : MonoBehaviour
	{
		public static Action<string> OnDelete;

		public GameObject Parent;

		public GameObject[] Originals;

		public string MeshAssetPath;

		//public void OnDestroy()
		//{
		//}

		//private void Start()
		//{
		//}

		//private void Update()
		//{
		//}

		//private void OnDrawGizmos()
		//{
		//}

		public void CallOnDelete()
		{
			if (Application.isEditor && !Application.isPlaying && OnDelete != null)
			{
				OnDelete(MeshAssetPath);
			}
		}

	}
}
